/*
 * @Author: liuhandong
 * @Date: 2020-9-24 21:27:20
 */
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public interface IFaceBlendProvider : System.IDisposable
{
    event System.Action onTrackingStateChange;
    void FillCoeffects(Dictionary<ARFaceBlend.Coeffect, float> container);
    bool IsTracking();
}

public class ARFaceBlend : MonoBehaviour
{
    public enum Coeffect
    {
        BrowDownLeft,
        BrowDownRight,
        BrowInnerUp,
        BrowOuterUpLeft,
        BrowOuterUpRight,
        CheekPuff,
        CheekSquintLeft,
        CheekSquintRight,
        EyeBlinkLeft,
        EyeBlinkRight,
        EyeLookDownLeft,
        EyeLookDownRight,
        EyeLookInLeft,
        EyeLookInRight,
        EyeLookOutLeft,
        EyeLookOutRight,
        EyeLookUpLeft,
        EyeLookUpRight,
        EyeSquintLeft,
        EyeSquintRight,
        EyeWideLeft,
        EyeWideRight,
        JawForward,
        JawLeft,
        JawOpen,
        JawRight,
        MouthClose,
        MouthDimpleLeft,
        MouthDimpleRight,
        MouthFrownLeft,
        MouthFrownRight,
        MouthFunnel,
        MouthLeft,
        MouthLowerDownLeft,
        MouthLowerDownRight,
        MouthPressLeft,
        MouthPressRight,
        MouthPucker,
        MouthRight,
        MouthRollLower,
        MouthRollUpper,
        MouthShrugLower,
        MouthShrugUpper,
        MouthSmileLeft,
        MouthSmileRight,
        MouthStretchLeft,
        MouthStretchRight,
        MouthUpperUpLeft,
        MouthUpperUpRight,
        NoseSneerLeft,
        NoseSneerRight,
        TongueOut,
        Count
    }
    [SerializeField]
    float m_CoefficientScale = 100.0f;

    public float coefficientScale
    {
        get { return m_CoefficientScale; }
        set { m_CoefficientScale = value; }
    }

    private IFaceBlendProvider mProvider = null;
    private SkinnedMeshRenderer mCachedRenderer = null;
    private List<int> mBlendShapeIndexForCoeffects = new List<int>();

    private Dictionary<Coeffect, float> mCoeffects = new Dictionary<Coeffect, float>();

    private void _UpdateCoeffects()
    {
        if(mProvider != null)
        {
            mCoeffects.Clear();
            mProvider.FillCoeffects(mCoeffects);
            _ApplyCoeffects();
        }
    }

    private void _ApplyCoeffects()
    {
        if(mCachedRenderer == null || !mCachedRenderer.enabled || mCachedRenderer.sharedMesh == null)
        {
            return;
        }
        float scale = coefficientScale;
        foreach(var entry in mCoeffects)
        {
            int idx = mBlendShapeIndexForCoeffects[(int)entry.Key];
            if(idx >= 0)
            {
                mCachedRenderer.SetBlendShapeWeight(idx, entry.Value * scale);
            }
        }
    }

    private void Update()
    {
        _UpdateCoeffects();
    }

    private void OnEnable()
    {
        _UpdateCoeffects();
        _RefreshMeshVisibility();
    }

    private void Awake()
    {
        _CacheSkinedMesh();
        _InitProvider();
    }

    private void _SetProvider(IFaceBlendProvider provider)
    {
        if(mProvider != null)
        {
            mProvider.onTrackingStateChange -= _OnProviderStateChange;
            mProvider.Dispose();
        }
        mProvider = provider;
        if(mProvider != null)
        {
            mProvider.onTrackingStateChange += _OnProviderStateChange;
        }
        _OnProviderStateChange();
    }

    private void _RefreshMeshVisibility()
    {
        bool visible = mProvider != null && mProvider.IsTracking() && enabled;
        if(mCachedRenderer != null)
        {
            mCachedRenderer.enabled = visible;
        }
    }

    private void _OnProviderStateChange()
    {
        _RefreshMeshVisibility();
        _UpdateCoeffects();
    }

    private void _CacheSkinedMesh()
    {
        mBlendShapeIndexForCoeffects.Clear();
        while(mBlendShapeIndexForCoeffects.Count < (int)Coeffect.Count)
        {
            mBlendShapeIndexForCoeffects.Add(-1);
        }
        mCachedRenderer = GetComponent<SkinnedMeshRenderer>();
        if(mCachedRenderer == null)
        {
            mCachedRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
        }
        if(mCachedRenderer == null)
        {
            return;
        }
        string strPrefix = "blendShape2.";
        mBlendShapeIndexForCoeffects[(int)Coeffect.BrowDownLeft        ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browDown_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.BrowDownRight       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browDown_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.BrowInnerUp         ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browInnerUp");
        mBlendShapeIndexForCoeffects[(int)Coeffect.BrowOuterUpLeft     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browOuterUp_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.BrowOuterUpRight    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "browOuterUp_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.CheekPuff           ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "cheekPuff");
        mBlendShapeIndexForCoeffects[(int)Coeffect.CheekSquintLeft     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "cheekSquint_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.CheekSquintRight    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "cheekSquint_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeBlinkLeft        ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeBlink_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeBlinkRight       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeBlink_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookDownLeft     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookDown_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookDownRight    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookDown_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookInLeft       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookIn_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookInRight      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookIn_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookOutLeft      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookOut_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookOutRight     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookOut_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookUpLeft       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookUp_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeLookUpRight      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeLookUp_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeSquintLeft       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeSquint_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeSquintRight      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeSquint_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeWideLeft         ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeWide_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.EyeWideRight        ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "eyeWide_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.JawForward          ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "jawForward");
        mBlendShapeIndexForCoeffects[(int)Coeffect.JawLeft             ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "jawLeft");
        mBlendShapeIndexForCoeffects[(int)Coeffect.JawOpen             ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "jawOpen");
        mBlendShapeIndexForCoeffects[(int)Coeffect.JawRight            ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "jawRight");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthClose          ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthClose");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthDimpleLeft     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthDimple_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthDimpleRight    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthDimple_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthFrownLeft      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthFrown_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthFrownRight     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthFrown_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthFunnel         ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthFunnel");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthLeft           ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthLeft");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthLowerDownLeft  ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthLowerDown_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthLowerDownRight ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthLowerDown_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthPressLeft      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthPress_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthPressRight     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthPress_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthPucker         ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthPucker");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthRight          ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthRight");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthRollLower      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthRollLower");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthRollUpper      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthRollUpper");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthShrugLower     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthShrugLower");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthShrugUpper     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthShrugUpper");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthSmileLeft      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthSmile_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthSmileRight     ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthSmile_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthStretchLeft    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthStretch_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthStretchRight   ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthStretch_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthUpperUpLeft    ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthUpperUp_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.MouthUpperUpRight   ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "mouthUpperUp_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.NoseSneerLeft       ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "noseSneer_L");
        mBlendShapeIndexForCoeffects[(int)Coeffect.NoseSneerRight      ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "noseSneer_R");
        mBlendShapeIndexForCoeffects[(int)Coeffect.TongueOut           ]   = mCachedRenderer.sharedMesh.GetBlendShapeIndex(strPrefix + "tongueOut");
    }

    private void _InitProvider()
    {
        var face = GetComponent<UnityEngine.XR.ARFoundation.ARFace>();
        if(ARManager.Instance.IsSupportARKitFaceBlend())
        {
            _SetProvider(new FaceBlendARKitProvider(face));
            Debug.Log("ARFaceBlend._InitProvider ----> Init IOS Provider");
        }
        else
        {
            _SetProvider(new FaceBlendTestProvider());
        }
    }
}